#include <vector>
#include <fstream>
#include <assert.h>
#include <stdlib.h>
#include <algorithm>
#include <stdio.h>
#include <errno.h>
#include <unordered_set>
#include <mutex>


#include "memalloc.hpp"
#include "memalloc.h"




#ifdef LINUX
#include <pthread.h>
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
#define M_LOCK(x) pthread_mutex_lock(&x)
#define M_UNLOCK(x) pthread_mutex_unlock(&x)
#else
std::mutex lock;
#define M_LOCK(x) (x).lock()
#define M_UNLOCK(x) (x).unlock()
#endif

#ifdef DEBUG
std::unordered_set<size_t> ids_;
std::unordered_set<size_t>::iterator ids_end = ids_.end();
#endif

int ma_init(size_t mem, uint64_t swap, const char* swap_path) {
        M_LOCK(lock);
	try {
		MemAlloc::Init(mem, swap, swap_path);
#ifdef DEBUG
		ids_.clear();
#endif
                M_UNLOCK(lock);
		return 1;
	}
	catch (MemAllocException& e) {
		fprintf(stderr, "%s\n", e.what());
		errno = e.GetErrorCode();
	}
	catch (std::bad_alloc& e) {
		fprintf(stderr, "%s\n", e.what());
		errno = ENOMEM;
	}
	catch (std::ios_base::failure& e) {
		fprintf(stderr, "%s\n", e.what());
		errno = EACCES;
	}
	catch (...) {
		fprintf(stderr, "Unknown std::exception\n");
	}
        M_UNLOCK(lock);
	return 0;
}

void ma_deinit() {
	try {
                M_LOCK(lock);
                MemAlloc::Deinit();
	}
	catch (std::exception& e) {
		fprintf(stderr, "%s\n", e.what());
	}
	catch (...) {
		 fprintf(stderr, "Unknown std::exception\n");
	}
        M_UNLOCK(lock);

}

size_t ma_alloc(size_t sz) {
	try {
                M_LOCK(lock);
		SmartPtr<char> *sp_id = MemAlloc::Alloc<char>(sz);
		size_t id = (size_t)sp_id;
#ifdef DEBUG
		ids_.insert(id);
#endif
		M_UNLOCK(lock);
		return id;
	}
	catch (MemAllocException& e) {
                M_UNLOCK(lock);
		fprintf(stderr, "%s\n", e.what());
		MemAllocException::ErrorCode code = e.GetErrorCode();
		if (code == MemAllocException::E_NOMEM)
			errno = ENOMEM;
		else if (code == MemAllocException::E_2BIG)
			errno = E2BIG;
		else if (code == MemAllocException::E_MLINK)
			errno = EMLINK;
		else if (code == MemAllocException::E_UNINITIALIZED)
		        errno = ECANCELED;
		return 0;
	}
	catch (std::exception& e) {
            M_UNLOCK(lock);
		fprintf(stderr, "%s\n", e.what());
		return 0;
	}
	catch (...) {
            M_UNLOCK(lock);
		fprintf(stderr, "Unknown std::exception\n");
		return 0;
	}
}

int ma_free(size_t id) {
        M_LOCK(lock);
	try {

#ifdef DEBUG
		if (!ids_.erase(id)) {
			errno = EFAULT;
			M_UNLOCK(lock);
			return 0;
		}
#endif
		SmartPtr<char> *sp_id = (SmartPtr<char>*) id;
		MemAlloc::Free(*sp_id);
		delete sp_id;
                M_UNLOCK(lock);
		return 1;
	}
	catch (std::exception& e) {
		fprintf(stderr, "%s\n", e.what());
	}
	catch (...) {
		  fprintf(stderr, "Unknown std::exception\n");
	}
	M_UNLOCK(lock);
	return 0;
}

void* ma_get(size_t id) {
        M_LOCK(lock);
	try {
#ifdef DEBUG
		if (ids_.find(id) == ids_end) {
			errno = EFAULT;
			M_UNLOCK(lock);
			return 0;
		}
#endif
		
		SmartPtr<char> *sp_id = (SmartPtr<char>*) id;
		void* ret = sp_id->Get();
                M_UNLOCK(lock);
                return ret;
	}
	catch (MemAllocException& e) {
            M_UNLOCK(lock);
		fprintf(stderr, "%s\n", e.what());
		MemAllocException::ErrorCode code = e.GetErrorCode();
		if (code == MemAllocException::E_NOMEM)
			errno = ENOMEM;
		else if (code == MemAllocException::E_NOBUFS)
			errno = ENOBUFS;
	}
	catch (std::ios_base::failure& e) {
		fprintf(stderr, "%s\n", e.what());
		errno = EBADF;
	}
	catch (std::exception& e) {
		fprintf(stderr, "%s\n", e.what());
	}
	catch (...) {
		fprintf(stderr, "Unknown std::exception\n");
	}
        M_UNLOCK(lock);
	return 0;
}

int ma_release(size_t id) {
	try {
		M_LOCK(lock);
#ifdef DEBUG
		if (ids_.find(id) == ids_end) {
			errno = EFAULT;
			M_UNLOCK(lock);
			return 0;
		}
#endif
		
		SmartPtr<char> *sp_id = (SmartPtr<char>*) id;
		sp_id->Release();
                M_UNLOCK(lock);
		return 1;
	}
	catch (std::exception& e) {
            M_UNLOCK(lock);
		fprintf(stderr, "%s\n", e.what());
	}
	catch (...) {
            M_UNLOCK(lock);
		fprintf(stderr, "Unknown std::exception\n");
	}
	return 0;
}
